Angular5 で bootstrap-datepickerを使う #serverless #adventcalendar
Web アプリを開発しているときに、日付の選択をカレンダーで行いたいシーンがあると思います。いろいろな選択肢がありますが、今回は bootstrap-datepicker を使ってみます。また、bootstrap-datepicker で選択した日付情報は input
要素の value に設定されますが、この値を Angualr アプリケーションから使えるよう設定します。
バージョン情報
ライブラリ/フレームワーク名 | バージョン | 役割 |
---|---|---|
Angular | 5.1.1 | Webアプリケーションの構築フレームワークとして使う |
bootstrap | 4.0.0-beta.2 | CSS利用のため。Card UI が使いたかったためbetaですがver4を採用しています |
jquery | 3.2.1 | bootstrap が要求します |
popper.js | 1.13.0 | bootstrap が要求します |
bootstrap-datepicker | 1.7.1 | 日付の選択でカレンダーUIを適用するために利用しています |
作業の流れ
- Angular アプリケーションを作成する
- ライブラリをインストールする
- カレンダーUIを使うコンポーネントを作成する
- カレンダーで選択した値がAngularアプリケーションで利用できることを確認する
Angular アプリケーションを作成する
ng new angular-datepicker cd angular-datepicker ng version Angular CLI: 1.6.1 Node: 8.5.0 OS: darwin x64 Angular: 5.1.1 ... animations, common, compiler, compiler-cli, core, forms ... http, language-service, platform-browser ... platform-browser-dynamic, router @angular/cli: 1.6.1 @angular-devkit/build-optimizer: 0.0.36 @angular-devkit/core: 0.0.22 @angular-devkit/schematics: 0.0.42 @ngtools/json-schema: 1.1.0 @ngtools/webpack: 1.9.1 @schematics/angular: 0.1.11 @schematics/schematics: 0.0.11 typescript: 2.4.2 webpack: 3.10.0
ライブラリをインストールする
cd angular-datepicker npm install --save [email protected] npm install --save jquery popper.js npm install --save bootstrap-datepicker
.angular-cli.json
を編集します。
{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "project": { "name": "angular-datepicker" }, "apps": [ { "assets": [ "favicon.ico", { "glob": "jquery.slim.min.js.map", "input": "../node_modules/jquery/dist/", "output": "./" }, { "glob": "popper.min.js.map", "input": "../node_modules/popper.js/dist/umd/", "output": "./" }, { "glob": "bootstrap.min.js.map", "input": "../node_modules/bootstrap/dist/js/", "output": "./" } ], "styles": [ "../node_modules/bootstrap/dist/css/bootstrap.min.css", "../node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker.css", "styles.css" ], "scripts": [ "../node_modules/jquery/dist/jquery.slim.min.js", "../node_modules/popper.js/dist/umd/popper.min.js", "../node_modules/bootstrap/dist/js/bootstrap.min.js", "../node_modules/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js" ], "environmentSource": "environments/environment.ts", "environments": { "dev": "environments/environment.ts", "prod": "environments/environment.prod.ts" } } ], ... }
これで、bootstrap-datepicker を利用する準備が整いました。
bootstrap-datepicker を利用するコンポーネントを作成
ng g component datepicker
<div class="container-fluid"> <div class="card"> <div class="card-header"> <span>カレンダー入力</span> </div> <div class="card-body"> <div class="form-group row"> <label for="datepicker" class="col-sm-2 col-form-label">bootstrap-datepicker</label> <div id="datepicker" class="col-sm-4"> <input type="text" class="form-control date" [(ngModel)]="date"> </div> <div class="col-sm-6 align"> <span> バインドされているモデルの値:{{date}} </span> </div> </div> </div> </div> </div>
import {Component, OnInit} from '@angular/core'; import * as $ from 'jquery'; import 'bootstrap-datepicker'; @Component({ selector: 'app-datepicker', templateUrl: './datepicker.component.html', styleUrls: ['./datepicker.component.css'] }) export class DatepickerComponent implements OnInit { date: string; constructor() { } ngOnInit() { this.date = this.formatDate(new Date()); $('#datepicker .date').datepicker({ format: 'yyyy-mm-dd' }); } private formatDate(date) { let format = 'YYYY-MM-DD'; format = format.replace(/YYYY/g, date.getFullYear()); format = format.replace(/MM/g, ('0' + (date.getMonth() + 1)).slice(-2)); format = format.replace(/DD/g, ('0' + date.getDate()).slice(-2)); if (format.match(/S/g)) { const milliSeconds = ('00' + date.getMilliseconds()).slice(-3); const length = format.match(/S/g).length; for (let i = 0; i < length; i++) { format = format.replace(/S/, milliSeconds.substring(i, i + 1)); } } return format; } }
jQuery で カレンダーUIの input
要素を選択し、datepickerを呼び出すことで画面上で利用できるようになります。その処理を、 Angular の ngOnInit で行っています。これでカレンダーが利用できるようになるはずです。
たしかにカレンダーUIを呼び出せていることがわかりますが、「バインドされているモデルの値」が更新されません。Angular では、HTML上で [(ngModel)]=変数名
のように書くと双方向バインディングが有効になり、カレンダーで選択された値が変数(今回の場合は date)にセットされることを期待しますが、うまく変数に入らないようです。どうやら、bootstrap-datepicker を使う場合、値が変更されたという通知(onChange)がうまく Angular 側に伝搬されないようです。そこで回避策を仕込みます。
カレンダーで選択した日付が Angular で利用できることを確認する
フォーカスが外れたタイミングで明示的に値をセットするようにします。
<div class="container-fluid"> <div class="card"> <div class="card-header"> <span>カレンダー入力</span> </div> <div class="card-body"> <div class="form-group row"> <label for="datepicker" class="col-sm-2 col-form-label">bootstrap-datepicker</label> <div id="datepicker" class="col-sm-4"> <input type="text" class="form-control date" [(ngModel)]="date" #datePicker (blur)="date = datePicker.value"> </div> <div class="col-sm-6 align"> <span> バインドされているモデルの値:{{date}} </span> </div> </div> </div> </div> </div>
#datePicker (blur)="date = datePicker.value"
を追加しました。 #datePicker
というAngularが理解できる名前をつけ、フォーカスが外れた時、その value を date
にセットしています。この処理により、カレンダーUIによってユーザーが日付を入力すると、その値が意図どおりAngularの変数にもセットされます。
このあと、フォーカスを外すと…
「バインドされているモデルの値」が更新されていることがわかります。これで、Angularのプログラムで日付情報が利用できます。
おわりに
サーバーレス開発でシングルページアプリケーションを開発するときは、なるべくライブラリの組み合わせや相性の問題に遭遇したくないものです。とはいえよりリッチにしていくために様々なツールを導入した結果、思いもよらぬ動作をしてしまうこともあると思います。根本的な解決を待つというのが手段のひとつですが、自力で解決してプルリクエストを送る、回避策を模索する、利用するツールを変えてみるという手もあります。今回は Angular と bootstrap-datepicker の組み合わせで発生した課題に対して回避策を取りました。同じような組み合わせの問題で困っている方の参考になれば幸いです。